package com.heaven1949.tmall.common.web.aspect;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.heaven1949.tmall.common.web.annotation.Idempotent;
import com.heaven1949.tmall.common.web.exception.ApiAssert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 幂等性校验切面
 *
 * @author zhangjian
 * @date 2025-11-13 14:20:55
 */
@Slf4j
@Aspect
@Component
public class IdempotentAspect {

    public static final String REPEAT_PARAMS = "repeatParams";
    public static final String REPEAT_TIME = "repeatTime";
    public static final String REPEAT_KEY = "sys:repeat_key:";
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 获取HttpServletRequest对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        ApiAssert.isFalse("请勿重复提交", volidateRepeatSubmit(request, joinPoint, idempotent));
        return joinPoint.proceed();
    }

    private Boolean volidateRepeatSubmit(HttpServletRequest request, ProceedingJoinPoint joinPoint, Idempotent idempotent) {
        // 获取本次参数及系统时间
        try {
            Object[] args = joinPoint.getArgs();
            Map<String, Object> nowDataMap = new HashMap<>(2);
            nowDataMap.put(REPEAT_PARAMS, JSON.toJSONString(args));
            nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
            // 请求地址（作为存放cache的key值）
            String url = request.getRequestURI();
            String token = request.getHeader(idempotent.key());
            if (StringUtils.isBlank(token)) {
                return false;
            }
            StringBuffer repeatKey = new StringBuffer();
            repeatKey.append(REPEAT_KEY).append(url).append(StringPool.COLON).append(token);
            Object repeatValue = redisTemplate.opsForValue().get(repeatKey.toString());
            log.info("*********************getkey:{}", repeatKey.toString());
            log.info("*********************getValue:{}", JSON.toJSONString(repeatValue));
            if (Objects.nonNull(repeatValue)) {
                Map<String, Object> preDataMap = (Map<String, Object>) repeatValue;
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, idempotent.intervalTime())) {
                    return true;
                }
            }
            redisTemplate.opsForValue().set(repeatKey.toString(), nowDataMap, idempotent.intervalTime(), TimeUnit.SECONDS);
            log.info("*********************setkey:{}", repeatKey.toString());
            log.info("*********************setValue:{}", JSON.toJSONString(nowDataMap));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return StringUtils.equals(nowParams, preParams);
    }

    /**
     * 判断两次间隔时间
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, long expireSeconds) {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        return (time1 - time2) < (expireSeconds * 1000);
    }

}